在大型網站應用上,Cache的使用絕對是讓人又愛又恨,若是用的好,可以讓網站的Performance大大提升,但若不謹慎使用Cache的話,最後可能會發現自己的網站怎麼更新都是舊資料。因此Cache的使用絕對必須審慎拿捏,用在關鍵點,而且一但用了Cache一定要有對應的資料更新機制,這樣才能讓Cache發揮最大的功效。
※新增Cache Role
在之前的文章中,我們已經新增了Web Role用來發行網站,而今天我們要在同樣的專案中新增一個Cache Role用來提供我們的Cache服務。
 
 
 
※替網站加入Cache
接下來我們將實作一個模擬長時間執行的資料查詢,並且替這個服務加上Cache
使用Nuget加入Azure cache的Library

修改config,將[cache cluster role name]改為我們剛剛新增的Role名稱
    <dataCacheClients>
      <dataCacheClient name="default">
        <!--To use the in-role flavor of Windows Azure Caching, set identifier to be the cache cluster role name -->
        <!--To use the Windows Azure Caching Service, set identifier to be the endpoint of the cache cluster -->
        <autoDiscover isEnabled="true" identifier="ApiSample.Cache" />
        <!--<localCache isEnabled="true" sync="TimeoutBased" objectCount="100000" ttlValue="300" />-->
        <!--Use this section to specify security settings for connecting to your cache. This section is not required if your cache is hosted on a role that is a part of your cloud service. -->
        <!--<securityProperties mode="Message" sslEnabled="false">
          <messageSecurity authorizationInfo="[Authentication Key]" />
        </securityProperties>-->
      </dataCacheClient>
    </dataCacheClients>    
撰寫一個模擬長時間查詢的服務
    public interface ILongTimeService
    {
        string GetLongTimeData();
    }
    public class LongTimeService : ILongTimeService
    {
        public string GetLongTimeData()
        {
            System.Threading.Thread.Sleep(10 * 1000);
            return DateTime.Now.ToString();
        }
    }
增加一個Controller提供資料,並且加上Cache,同時我們加上StopWatch觀察查詢資料需要的時間
    public class SampleController : Controller
    {            
        public SampleController(ISampleService sampleService, ILongTimeService longTimeService)
        {
            this.SampleService = sampleService;
            this.LongTimeService = longTimeService;
        }  
        public ActionResult GetLongTimeData()
        {
            //// Calculate execute time
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            //// Emulate long time processing data
            DataCacheFactory cacheFactory = new DataCacheFactory();
            DataCache cache = cacheFactory.GetDefaultCache();
            object result = cache.Get("longTimeData");
            if (result == null)
            {
                result = this.LongTimeService.GetLongTimeData();
                cache.Add("longTimeData", result);
            }
            stopWatch.Stop();
            TimeSpan ts = stopWatch.Elapsed;
            string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                ts.Hours, ts.Minutes, ts.Seconds,
                ts.Milliseconds / 10);
            return Json(
                new
                {
                    Result = result,
                    ExecuteTime = elapsedTime
                }, JsonRequestBehavior.AllowGet);
        }
    }
其實cacheFactory.GetDefaultCache();代表取得default這組cache,也可以使用cacheFactory.GetCache("cache name");取得其它Cache資料。
 
 
測試成功!我們的Cache服務生效了,大家還可以依據自己的需求進一步的調整Cache的時間長短。
延伸閱讀:
* How to cache service
※使用AOP實作CacheInterceptor
除了直接在程式碼直接撰寫Cache的讀取程式碼之外,其實我們也可以將Cache抽出作為Interceptor,這麼一來我們只要在需要的Class上加上Attribute就可以完成Cache的新增,並且讓Cache統一並分離於商業邏輯之外,也增加了重複使用的容易性!
在Extensions專案新增CacheInterceptor
    public class CacheInterceptor : IInterceptor
    {
        private string section;
        public CacheInterceptor()
            : this(string.Empty)
        {
        }
        public CacheInterceptor(string section)
        {
            this.section = section;
        }
        public void Intercept(IInvocation invocation)
        {
            DataCacheFactory cacheFactory = new DataCacheFactory();
            //// Get cache by section
            DataCache cache;
            if (string.IsNullOrWhiteSpace(this.section))
            {
                cache = cacheFactory.GetDefaultCache();
            }
            else
            {
                cache = cacheFactory.GetCache(this.section);
            }
            //// Get cache or set by proceed method
            var typeName = invocation.TargetType.FullName;
            var methodName = invocation.Method.Name;
            var cacheKey = string.Format("{0}-{1}", typeName, methodName);
            var result = cache.Get(cacheKey);
            if (result == null)
            {
                invocation.Proceed();
                result = invocation.ReturnValue;
                cache.Add(cacheKey, result);
            }
            else
            {
                invocation.ReturnValue = result;
            }
        }
    }
修改BL的ServiceModule,讓LongTimeService使用CacheInterceptor
    public class ServiceModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            var service = Assembly.Load("ApiSample.BL.Services");
            builder.RegisterAssemblyTypes(service)
                   .AsImplementedInterfaces()
                   .EnableInterfaceInterceptors();
            builder.RegisterType<ProductService>()
                   .As<IProductService>()
                   .EnableInterfaceInterceptors()
                   .InterceptedBy(typeof(LogInterceptor), typeof(AuthInterceptor));
            builder.RegisterType<LongTimeService>()
                   .As<ILongTimeService>()
                   .EnableInterfaceInterceptors()
                   .InterceptedBy(typeof(CacheInterceptor));
        }
    }
修改Controller,將原本手動新增的Cache程式碼移除
    public ActionResult GetLongTimeData()
    {
        //// Calculate execute time
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();
        var result = this.LongTimeService.GetLongTimeData();
        stopWatch.Stop();
        TimeSpan ts = stopWatch.Elapsed;
        string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
            ts.Hours, ts.Minutes, ts.Seconds,
            ts.Milliseconds / 10);
        return Json(
            new
            {
                Result = result,
                ExecuteTime = elapsedTime
            }, JsonRequestBehavior.AllowGet);
    }
發行上雲端,測試一切正常,如此一來不但讓我們的程式碼更加的乾淨,也讓要增加Cache變成一件輕鬆的事情囉!
※本日小結
透過Cache的幫助,我們可以讓需要花費大量資源才能產生資料的服務成本降低,也讓網站整體的效能變好,但還是必須謹慎的使用以避免Cache過於繁雜而難於管理,Azure提供的分散式Cache服務也讓Cloud Service可以共用Cache服務,更甚至透過多個Cache Instance來實現高可用性,相當的方便,大家可以多加利用!關於今天的內容,歡迎大家一起討論喔^_^